import json

import pytest
from app import services
from fastapi import status


@pytest.fixture
def test_responses_detailed():
    """В формате RecipeDBSchema"""
    return [
        {
            "id": 1,
            "title": "Яичница",
            "description": "Очень вкусно!",
            "ingredients": "Яйца, 2шт.",
            "cooking_time": 5,
            "view_count": 0,
            "created_date": "2024-04-10T14:41:58.860000Z",
        },
        {
            "id": 2,
            "title": "Яичница с ветчиной",
            "description": "Очень вкусно!",
            "ingredients": "Яйца, 2шт.; ветчина, 100 гр.",
            "cooking_time": 10,
            "view_count": 0,
            "created_date": "2024-04-10T14:41:58.860000Z",
        },
    ]


@pytest.fixture
def test_responses_list():
    """В формате RecipeListSchema"""
    return [
        {
            "id": 1,
            "title": "Яичница",
            "cooking_time": 5,
            "view_count": 0,
        },
        {
            "id": 2,
            "title": "Яичница с ветчиной",
            "cooking_time": 10,
            "view_count": 0,
        },
    ]


@pytest.fixture
def test_request_item():
    return {
        "title": "Яичница",
        "description": "Очень вкусно!",
        "ingredients": "Яйца, 2шт.",
        "cooking_time": 5,
    }


@pytest.fixture
def test_response_item(test_responses_detailed):
    return test_responses_detailed[0]


def test_create_recipe(test_app, test_request_item, test_response_item, monkeypatch):

    # Подменяем реальные функции сервисного слоя имитацией, которая вернёт нужные значения.
    # Таким образом, мы изолируем код endpoint'а и протестируем его отдельно.
    async def mock_create(payload):
        return 1

    async def mock_get(recipe_id, increase_counter=False):
        return test_response_item

    monkeypatch.setattr(services, "create", mock_create)
    monkeypatch.setattr(services, "get", mock_get)

    # В коде эндпоинта будут вызваны "поддельные" функции сервисного слоя, которые мы определили выше.
    response = test_app.post("/recipes/", content=json.dumps(test_request_item))

    assert response.status_code == status.HTTP_201_CREATED
    assert response.json() == test_response_item


def test_create_recipe_invalid_json(test_app):
    resp = test_app.post("/recipes/", content=json.dumps({"title": "Only title"}))
    assert resp.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY


def test_get_recipe(test_app, test_response_item, monkeypatch):
    async def mock_get(recipe_id, increase_counter=False):
        return test_response_item

    monkeypatch.setattr(services, "get", mock_get)

    response = test_app.get(f"/recipes/{test_response_item['id']}")

    assert response.status_code == status.HTTP_200_OK
    assert response.json() == test_response_item


def test_get_recipe_incorrect_id(test_app, monkeypatch):
    async def mock_get(recipe_id, increase_counter=False):
        return None

    monkeypatch.setattr(services, "get", mock_get)

    response = test_app.get("/recipes/123")
    assert response.status_code == status.HTTP_404_NOT_FOUND
    assert response.json()["detail"] == "Recipe not found"


def test_get_all_recipes(
    test_app, test_responses_detailed, test_responses_list, monkeypatch
):
    async def mock_get_all():
        # Возвращаем детальный вид "как из базы", он будет сокращен в эндпоинте
        return test_responses_detailed

    monkeypatch.setattr(services, "get_all", mock_get_all)

    response = test_app.get("/recipes/")
    assert response.status_code == status.HTTP_200_OK
    # NB: на вход в endpoint "из базы" передаются полные данные, а уже в ендпоинте
    # происходит приведение RecipeDBSchema к укороченной RecipeListSchema
    assert response.json() == test_responses_list


def test_update_recipe(test_app, test_response_item, monkeypatch):
    modified = test_response_item
    modified["title"] = "Updated title"
    modified["description"] = "Updated description"

    async def mock_get(recipe_id, increase_counter=False):
        return test_response_item

    async def mock_update(recipe_id, recipe):
        return modified["id"]

    monkeypatch.setattr(services, "get", mock_get)
    monkeypatch.setattr(services, "update", mock_update)

    response = test_app.patch(
        f"/recipes/{test_response_item['id']}/", content=json.dumps(modified)
    )
    assert response.status_code == status.HTTP_200_OK
    assert response.json() == modified


@pytest.mark.parametrize(
    "recipe_id, payload, status_code",
    [
        [1, {}, 422],
        [1, {"description": "bar"}, 422],
        [
            123,
            {
                "title": "foo",
                "description": "bar",
                "cooking_time": 1,
                "ingredients": "some",
            },
            404,
        ],
    ],
)
def test_update_note_invalid(test_app, monkeypatch, recipe_id, payload, status_code):
    async def mock_get(id_, increase_counter=False):
        return None

    monkeypatch.setattr(services, "get", mock_get)

    response = test_app.patch(
        f"/recipes/{recipe_id}/",
        content=json.dumps(payload),
    )
    assert response.status_code == status_code


def test_delete_recipe(test_app, test_response_item, monkeypatch):
    async def mock_get(recipe_id, increase_counter=False):
        return test_response_item

    async def mock_delete(recipe_id):
        return None

    monkeypatch.setattr(services, "get", mock_get)
    monkeypatch.setattr(services, "delete", mock_delete)

    response = test_app.delete(f"/recipes/{test_response_item['id']}")
    assert response.status_code == status.HTTP_204_NO_CONTENT


def test_remove_recipe_incorrect_id(test_app, monkeypatch):
    async def mock_get(recipe_id, increase_counter=False):
        return None

    monkeypatch.setattr(services, "get", mock_get)

    response = test_app.delete("/recipes/123")
    assert response.status_code == status.HTTP_404_NOT_FOUND
    assert response.json()["detail"] == "Recipe not found"
